package com.study.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.study.common.StatusEnum;
import com.study.mapper.ClassMapper;
import com.study.mapper.StudentMapper;
import com.study.model.entity.Class;
import com.study.model.entity.Monitor;
import com.study.model.entity.Student;
import com.study.service.StudentService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author Admin
 * @since 2021-11-21
 */
@Slf4j
@Service
public class StudentServiceImpl extends ServiceImpl<StudentMapper, Student> implements StudentService {

    @Autowired
    ClassMapper classMapper;

    @Override
    public List<Student> queryAll() {
        return this.list(
                Wrappers.<Student>lambdaQuery()
                        .eq(Student::getDeleteStatus, StatusEnum.NO.getCode()));
    }

    @Override
    public void add(Student student) {
        baseMapper.insert(student);
    }

    @Override
    public void delById(Long id) {
        Student student = Student.builder().id(1L).deleteStatus(StatusEnum.YES.getCode()).build();
        baseMapper.updateById(student);
    }

    @Override
    public void testOptimisticLocker() {
        // 测试乐观锁成功
        Student student = baseMapper.selectById(1L);
        student.setName("张三");
        student.setAge(20);
        baseMapper.updateById(student);
        // 测试乐观锁失败，多线程下
        Student student2 = baseMapper.selectById(2L);
        student2.setName("四");
        student2.setAge(20);
        // 模拟另外一个线程执行了插队操作
        Student student3 = baseMapper.selectById(2L);
        student3.setName("小四");
        student3.setAge(25);
        baseMapper.updateById(student3);
        // 如果没有乐观锁就会覆盖插队线程的值！
        baseMapper.updateById(student2);
    }

    @Override
    public void testQuery() {
        /* map查询---强烈不推荐，魔法值问题 */
        Map<String, Object> map = new HashMap<>();
        map.put("name", "张三");
        map.put("age", 20);
        List<Student> students = baseMapper.selectByMap(map);
        students.forEach(System.out::println);

        /* 分页查询 */
        //方式一
        Page<Student> page = new Page<>(1, 5);
        Page<Student> studentPage = baseMapper.selectPage(page, null);
        studentPage.getRecords().forEach(System.out::println);
        System.out.println(studentPage.getTotal());
        //方式二
        Page<Student> studentPage2 = baseMapper.page(page, "张三");
        studentPage2.getRecords().forEach(System.out::println);
        System.out.println(studentPage2.getTotal());
        //注意这里的page、studentPage、studentPage2实际上是一个内存地址，原因看说明文档第四条

        /* selectMaps---这个方法会将查询结果封装为一个Map */
        LambdaQueryWrapper<Student> selectMapsWrapper = Wrappers.<Student>lambdaQuery()
                .eq(Student::getDeleteStatus, StatusEnum.NO.getCode())
                .select(Student::getId, Student::getAge);
        List<Map<String, Object>> selectMaps = baseMapper.selectMaps(selectMapsWrapper);
        selectMaps.forEach(System.out::println);
        // 数据统计
        QueryWrapper<Student> selectMapsWrapper2 = new QueryWrapper<>(
                Student.builder().deleteStatus(StatusEnum.NO.getCode()).build())
                .select("class_id", "min(age) minAge", "max(age) maxAge", "avg(age) avgAge")
                // .eq("delete_status", StatusEnum.NO.getCode())不建议,可以将条件构建成对象，放在构造函数中，如上述写法
                .groupBy("class_id")
                .having("sum(age) < {0}", 500);
        List<Map<String, Object>> selectMaps2 = baseMapper.selectMaps(selectMapsWrapper2);
        selectMaps2.forEach(System.out::println);

        /* 条件构建器演示*/
        QueryWrapper<Student> wrapper = new QueryWrapper<>();
        // 下面展示SQL时，仅展示WHERE条件；展示代码时, 仅展示Wrapper构建部分
        // 2. 姓名为黄姓，且年龄大于等于20，小于等于40，且email字段不为空
        // name like '黄%' AND age BETWEEN 20 AND 40 AND email is not null
        wrapper.likeRight("name", "黄").between("age", 20, 40).isNotNull("email");
        // 3. 姓名为黄姓，或者年龄大于等于40，按照年龄降序排列，年龄相同则按照id升序排列
        // name like '黄%' OR age >= 40 order by age desc, id asc
        wrapper.likeRight("name", "黄").or().ge("age", 40).orderByDesc("age").orderByAsc("id");
        // 4.创建日期为2021年3月22日，并且直属上级的名字为李姓
        // date_format(gmt_create,'%Y-%m-%d') = '2021-03-22' AND manager_id IN (SELECT id FROM student WHERE name like '李%')
        // 条件表达式当需要传入自定义的SQL语句，或者需要调用数据库函数时，可用apply()方法进行SQL拼接
        wrapper.apply("date_format(gmt_create, '%Y-%m-%d') = {0}", "2021-03-22")  // 建议采用{index}这种方式动态传参, 可防止SQL注入
                .inSql("manager_id", "SELECT id FROM student WHERE name like '李%'");
        // 上面的apply, 也可以直接使用下面这种方式做字符串拼接，但当这个日期是一个外部参数时，这种方式有SQL注入的风险
        wrapper.apply("date_format(gmt_create, '%Y-%m-%d') = '2021-03-22'");
        // 5. 名字为王姓，并且（年龄小于40，或者邮箱不为空）
        // name like '王%' AND (age < 40 OR email is not null)
        wrapper.likeRight("name", "王").and(q -> q.lt("age", 40).or().isNotNull("email"));
        // 6. 名字为王姓，或者（年龄小于40并且年龄大于20并且邮箱不为空）
        // name like '王%' OR (age < 40 AND age > 20 AND email is not null)
        wrapper.likeRight("name", "王").or(
                q -> q.lt("age", 40)
                        .gt("age", 20)
                        .isNotNull("email")
        );
        // 7. (年龄小于40或者邮箱不为空) 并且名字为王姓
        // (age < 40 OR email is not null) AND name like '王%'
        wrapper.nested(q -> q.lt("age", 40).or().isNotNull("email"))
                .likeRight("name", "王");
        // 8. 年龄为30，31，34，35
        // age IN (30,31,34,35)
        wrapper.in("age", Arrays.asList(30, 31, 34, 35));
        // 或
        wrapper.inSql("age", "30,31,34,35");
        // 9. 年龄为30，31，34，35, 返回满足条件的第一条记录
        // age IN (30,31,34,35) LIMIT 1
        wrapper.in("age", Arrays.asList(30, 31, 34, 35)).last("LIMIT 1");
        // 10. 只选出id, name 列 (QueryWrapper 特有)
        // SELECT id, name FROM student;
        wrapper.select("id", "name");
        // 11. 选出id, name, age, email, 等同于排除 manager_id 和 gmt_create
        // 当列特别多, 而只需要排除个别列时, 采用上面的方式可能需要写很多个列, 可以采用重载的select方法，指定需要排除的列
        wrapper.select(Student.class, info -> {
            String columnName = info.getColumn();
            return !"gmt_create".equals(columnName) && !"class_id".equals(columnName);
        });

        /* Condition */
        // 条件构造器的诸多方法中，均可以指定一个boolean类型的参数condition，
        // 用来决定该条件是否加入最后生成的WHERE语句中，比如:
        String name = "黄"; // 假设name变量是一个外部传入的参数
        QueryWrapper<Student> conditionWrapper = new QueryWrapper<>();
        wrapper.like(StringUtils.isNotBlank(name), "name", name);
    }

    @Override
    public void update(Student student) {
        this.updateById(student);
    }

    @Override
    public void testUpdate() {
        Student student = new Student();
        student.setName("二蛋");
        LambdaUpdateWrapper<Student> wrapper = new LambdaUpdateWrapper<>();
        wrapper.likeRight(Student::getName, "夏");
        baseMapper.update(student, wrapper);
        //演示一下，把实体对象传入Wrapper，即用实体对象构造WHERE条件的案例
        Student whereStudent = new Student();
        whereStudent.setAge(40);
        whereStudent.setName("王");
        LambdaUpdateWrapper<Student> wrapper2 = new LambdaUpdateWrapper<>(whereStudent);
        Student student2 = new Student();
        student2.setEmail("share@baomidou.com");
        student2.setClassId(2L);
        baseMapper.update(student2, wrapper2);
        //演示一下，链式lambda条件构造器的使用
        // sql语句：UPDATE t_student SET email=? WHERE age=? AND (email LIKE ? AND name LIKE ?)
        LambdaUpdateChainWrapper<Student> wrapper3 = new LambdaUpdateChainWrapper<>(baseMapper);
        wrapper3.likeRight(Student::getEmail, "@qq.com")
                .like(Student::getName, "王")
                .set(Student::getEmail, "ff@baomidou.com")
                .setEntity(Student.builder().age(100).build())
                .update();
    }

    @Override
    public void testActiveRecord() {
        //ActiveRecord模式，通过操作实体对象，直接操作数据库表。
        //步骤：1.让实体类继承自Model 2.直接调用实体对象上的方法
        Student student = new Student();
        student.setId(15L);//这里设置id没用，因为设置了主键自增策略
        student.setName("我是AR猪");
        student.setAge(1);
        student.setEmail("ar@baomidou.com");
        student.setClassId(1L);
        boolean success = student.insert();
        System.out.println(success);
    }

    @Override
    public void testTypeHandler() {
        /*插入*/
        classMapper.insert(Class.builder().name("高三一班").monitor(Monitor.builder().name("亮亮").age(30).build()).build());
        /*查询*/
        Class aClass = classMapper.selectById(11L);
        System.out.println("aClass：" + aClass);
        /*自定义查询*/
        Class aClass2 = classMapper.queryById(11L);
        System.out.println("aClass2：" + aClass2);
        Class aClass3 = classMapper.queryClassById(11L);
        System.out.println("aClass3：" + aClass3);
        /*自定义新增*/
        classMapper.save(Class.builder().name("高三一班").monitor(Monitor.builder().name("亮亮").age(30).build()).build());
    }

}
